import type { Metadata } from 'next'; import Link from 'next/link'; import { notFound } from 'next/navigation'; import { t, tOpt } from '@/i18n'; import { api, isNotBuilt, isNotFound, safe } from '@/lib/api'; import { apiAnalytics } from '@/lib/api-analytics'; import { apiExplore } from '@/lib/api-explore'; import { fixed, formatValue, grouped } from '@/lib/format'; import { seoTitle } from '@/lib/seo'; import { routes } from '@/lib/site'; import type { IndicatorCard } from '@/lib/types'; import type { RegionResponse } from '@/lib/types-explore'; import { NotBuiltState } from '@/components/data/empty-state'; import { Section } from '@/components/data/section'; import { ACTION_CLS, PageHeader } from '@/components/explore/page-header'; import { GroupComparePicker } from '@/components/regions/group-compare-picker'; import { GroupHistory } from '@/components/regions/group-history'; import { MemberMap } from '@/components/regions/member-map'; import { MemberRanking } from '@/components/regions/member-ranking'; import { MembersTable } from '@/components/regions/members-table'; export const revalidate = 900; type Params = { slug: string }; async function load(slug: string): Promise { try { return await apiExplore.region(slug); } catch (e) { if (isNotFound(e)) return null; if (isNotBuilt(e)) return 'not-built'; throw e; } } export async function generateMetadata({ params }: { params: Promise }): Promise { const { slug } = await params; const data = await load(slug); if (!data || data === 'not-built') return { title: t('region.notFound'), robots: { index: false } }; const name = data.group.name ?? slug; const title = seoTitle.region(name); const description = t('region.description', { name, n: data.n_members }); const canonical = routes.region(data.group.slug ?? slug); return { title: { absolute: `${title} | ${t('site.name')}` }, description, alternates: { canonical }, openGraph: { title: `${title} | ${t('site.name')}`, description, url: canonical, type: 'article' }, twitter: { card: 'summary_large_image', title, description } }; } /** Extra indicators offered in the member ranking picker besides the headline ones. */ const EXTRA_RANKING = ['gdp-per-capita-ppp', 'population-growth', 'fertility-rate', 'renewable-electricity-share', 'internet-users', 'co2-per-capita', 'general-government-gross-debt-pct-gdp', 'median-age']; export default async function RegionPage({ params }: { params: Promise }) { const { slug } = await params; const data = await load(slug); if (data === null) notFound(); if (data === 'not-built') return ; const g = data.group; const name = g.name ?? slug; const gslug = g.slug ?? g.id; const kindLabel = tOpt(`regions.kindLabel.${g.kind ?? 'custom'}`, g.kind ?? ''); const isWorld = g.id === 'world'; const [countriesRes, extraInd, vsWorld, regions] = await Promise.all([safe(api.countries()), safe(apiExplore.indicators({ featured: true })), isWorld ? Promise.resolve(null) : safe(apiAnalytics.regionsCompare(gslug, 'world')), safe(apiExplore.regions())]); const shares = vsWorld?.shares[g.id] ?? vsWorld?.shares[gslug] ?? null; const otherDefault = gslug === 'g7' ? 'brics' : 'g7'; const countries = countriesRes?.items ?? []; const memberIds = data.members.map((m) => m.id); const aggs = Object.values(data.aggregates); const options: IndicatorCard[] = [...data.headline_indicators, ...(extraInd?.items ?? []).filter((i) => EXTRA_RANKING.includes(i.slug) || i.featured)].filter((i) => i.ranking_eligible !== false); const bySize = [...data.members].sort((a, b) => (b.values.population?.value ?? 0) - (a.values.population?.value ?? 0)); const compareSlugs = bySize.slice(0, 8).map((m) => m.slug ?? m.id); const compareAll = data.n_members <= 8; return ( <> = 2 ? ( {compareAll ? t('region.compareAll') : t('region.compareFirst', { n: compareSlugs.length })} ) : null } />
{shares ? (
{(['gdp_share_pct', 'population_share_pct'] as const).map((k) => { const v = shares[k]; return (
{k === 'gdp_share_pct' ? t('regions.compare.gdpShare') : t('regions.compare.popShare')}
{v != null ? `${fixed(v, 1)} %` : t('common.na')}
); })}
) : null} {aggs.length === 0 ? (

{t('common.noDataLong')}

) : (
{aggs.map((a) => (
{a.indicator.short_name ?? a.indicator.name} {tOpt(`regions.${a.kind}`, a.kind)}
{formatValue(a.value, a.indicator)}
{t('region.aggregates.n', { n: grouped(a.n ?? 0), year: a.year ?? '' })}
))}
)}
{countries.length ? :

{t('common.noDataLong')}

}
{vsWorld && Object.keys(vsWorld.history).length ? (
) : null}
{t('regions.compare.open')} →}>
{data.members.length ? :

{t('common.noDataLong')}

}
); }